<?php
namespace hema\wechat;

use app\common\model\Setting;
use hema\wechat\AesUtil;
use think\facade\Cache;
use hema\Http;
use app\common\model\Applet;
use app\common\model\Record;
use app\common\model\DivideAccount;

/**
 * 微信支付
 */
class Pay
{
    private $api_url = 'https://api.mch.weixin.qq.com';//接口域名
    private $version = 'v3'; //接口版本
    private $isp_config; // 微信支付服务商参数
    private $config; // 微信支付参数
    private $error;

    /**
     * 构造方法
     */
    public function __construct(array $config = [])
    {
        $this->isp_config = Setting::getItem('wxpayisp',0);
        $this->config = $config;
    }
    
    /********** V3接口 **********/
    //扣取手续费 按照0.6%计算
    private function serviceFee($fee)
    {
       return intval(($fee - ($fee * 6 / 1000)) * 100);
    }
    /**
     * 分账
     * $data：数组 =分账数据
     * [
     *  out_order_no：第三方订单号
     *  transaction_id:微信订单号
     *  total：分账总金额
     * ]
     * $applet_id=小程序编号
     * $delivery_fee=配送费分账金额
     */
    public function divide($data,$applet_id='',$delivery_fee=0)
    {
		$is_divide = false;//是否分账
		$total = $data['total'];//分账总金额
		$service_fee = 0;//平台分账金额（单位分）
		$agent_fee = 0;//代理分账金额（单位分）
		$agent_openid = '';//代理收款账号
		//判断外卖订单是否分账配送费
		if($delivery_fee > 0){
		    $total = $total - $delivery_fee;
		    $delivery_fee = $this->serviceFee($delivery_fee);//去掉手续费
            $is_divide = true; //配送费大于0 开启分账
        }
		$divide = Setting::getItem('divide',0); //分佣参数
		$applet = Applet::get($applet_id);//获取商家应用
		//如果开启分佣
        if($divide['extract'] > 0){
            $extract = $total * $divide['extract'] / 100;//抽取金额
            $service_fee = $this->serviceFee($extract);//去掉手续费
            if($divide['agent_extract'] > 0){
                //判断商家是否有代理商
                if($applet['agent_id'] > 0){
                    if($account = DivideAccount::withoutGlobalScope()->where('applet_id',$applet_id)->find()){
                        if(!empty($account['open_id'])){
                            $agent_openid = $account['open_id'];
                            $agent_fee = $this->serviceFee($extract * $divide['agent_extract'] / 100);//去掉手续费
                            $service_fee = $service_fee - $agent_fee;
                        }
                    }
                }
            }
            $is_divide = true; //开启分账
        }
        //判断是否要进行分账
        if(!$is_divide){
            $this->error = '不用分账';
            return false;
        }
        //***************** 添加分账接收方 *********************//
        $receivers = [];//收款方账号列表
        //添加平台收佣账号
		if(($service_fee + $delivery_fee) > 0){
            if(!$this->addReceivers($applet_id)) {
                $this->error = '添加平台分账接收方失败';
                return false;
            }
            $webpay = Setting::getItem('webpay',0)['wx']; //平台微信支付参数
            $receivers[] = [
                'type' => 'MERCHANT_ID',//分账接收方类型 MERCHANT_ID=商户号 PERSONAL_OPENID=个人openid
                'account' => $webpay['mch_id'],//分账接收方账号
                'amount' => $service_fee + $delivery_fee,//分账金额
                'description' => '分佣给平台',//分账描述
            ];
		}
		//添加代理收佣账号
		if($agent_fee > 0 and !empty($agent_openid)){
		    if(!$this->addReceivers($applet_id,false,$agent_openid)) {
                $this->error = '添加代理分账接收方失败';
                return false;
            } 
            $receivers[] = [
                'type' => $this->config['is_sub'] == 1 ? 'PERSONAL_SUB_OPENID':'PERSONAL_OPENID',//分账接收方类型 
                'account' => $agent_openid,//分账接收方账号
                'amount' => $agent_fee,//分账金额
                'description' => '分佣给代理',//分账描述
            ];
		}
        //***************** 请求分账 *********************//
        if(sizeof($receivers) == 0){
            $this->error = '收款方账号列表为空';
            return false;
        }
        if(!$this->profitSharing($data['transaction_id'],$data['out_order_no'],$receivers)) {
            $this->error = '请求分账失败';
            return false;
        }
        //***************** 添加交易记录 *********************//
        $record_log = [];//交易流水记录
        //是否增加平台分红记录（分佣）
        if(($service_fee - $agent_fee) > 0){
            $money = sprintf("%.2f",$service_fee / 100);//计算金额
            //平台分红（分佣）记录
            array_push($record_log,[
                'user_type' => 40, //平台
                'action' => 50, //分红
                'order_no' => $data['out_order_no'],
                'money' => $money,
                'remark' => '交易分佣'
            ]);
            //商户扣费记录
            array_push($record_log,[
                'user_type' => 20,//用户类型(10商家会员 20站点会员 30代理 40平台)
                'action' => 20, //扣减
                'order_no' => $data['out_order_no'],
                'money' => $money,
                'user_id' => $applet['user_id'],
                'remark' => '交易服务费'
            ]);
        }
        //是否增加配送费记录
        if($delivery_fee > 0){
            $money = sprintf("%.2f",$delivery_fee / 100);//计算金额
            //平台收取记录
            array_push($record_log,[
                'user_type' => 40, //平台
                'action' => 50, //分红
                'order_no' => $data['out_order_no'],
                'money' => $money,
                'remark' => '第三方配送费'
            ]);
            //商户扣费记录
            array_push($record_log,[
                'user_type' => 20,//用户类型(10商家会员 20站点会员 30代理 40平台)
                'action' => 20, //扣减
                'order_no' => $data['out_order_no'],
                'money' => $money,
                'user_id' => $applet['user_id'],
                'remark' => '第三方配送费'
            ]);
        }
        //是否增加代理分佣记录
        if($agent_fee > 0){
            $money = sprintf("%.2f",$agent_fee / 100);//计算金额
            //平台收取记录
            array_push($record_log,[
                'user_type' => 30, //代理
                'action' => 50, //分红
                'order_no' => $data['out_order_no'],
                'money' => $money,
                'user_id' => $applet['agent_id'],
                'remark' => '交易分佣'
            ]);
        }
        //批量增加交易记录
        $model = new Record;
        if(!$model->saveAll($record_log)){
            $this->error = '添加交易记录失败';
            return false; 
        }
        return true;
    }
    /**
     * 请求分账API
     */
    private function profitSharing($transaction_id,$out_order_no,$receivers)
    {
        //服务商
        $params = [
            'transaction_id' => $transaction_id,//微信订单号
            'out_order_no' => $out_order_no,//商户分账单号
            'receivers' => $receivers,
            'unfreeze_unsplit' => true,//是否解冻剩余未分资金
        ];
        if($this->config['is_sub'] == 1){
            //服务商
			$params['appid'] = $this->isp_config['app_id'];//服务商应用ID
			$params['sub_appid'] = $this->config['app_id'];//子商户应用ID
			$params['sub_mchid'] = $this->config['mch_id'];//子商户号
			$is_isp = true;
		}else{
		    //直连商户
			$params['appid'] = $this->config['app_id'];//小程序ID
			$is_isp = false;
		}
		
		
		
        $params = hema_json($params);
        $url = $this->getUrl('profitsharing/orders');
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params,$is_isp),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
            'Wechatpay-Serial:' . $this->isp_config['serial_no'],
        ];
        return $this->result(json_decode(Http::post($url, $params,[],$headers),true));
    }
    /**
     * 添加分账接收方API
     * $is_mchid = 接收方是否是商户 $account = 接收账号
     */
    private function addReceivers($applet_id,$is_mchid=true,$account='')
    {
        if($this->config['is_sub'] == 1){
            //服务商
			$params['appid'] = $this->isp_config['app_id'];//服务商应用ID
			$params['sub_appid'] = $this->config['app_id'];//子商户应用ID
			$params['sub_mchid'] = $this->config['mch_id'];//子商户号
			if($is_mchid){
			    $webpay =  Setting::getItem('webpay',0)['wx'];
			    $params['type'] = 'MERCHANT_ID';//分账接收方类型
			    $params['account'] = $webpay['mch_id'];//分账接收方账号
                $params['name'] = $this->getEncrypt($webpay['name']); //（加密）分账个人接收方姓名 分账接收方类型是MERCHANT_ID时，是商户全称（必传）
			}else{
			    $params['type'] = 'PERSONAL_SUB_OPENID';//分账接收方类型
			    $params['account'] = $account;//分账接收方账号
			}
			$params['relation_type'] = 'SERVICE_PROVIDER'; //与分账方的关系类型 服务商
			$is_isp = true;
			$serial_no = $this->isp_config['serial_no'];
		}else{
		    //直连商户
			$params['appid'] = $this->config['app_id'];//小程序ID
			if($is_mchid){
			    $webpay =  Setting::getItem('webpay',0)['wx'];
			    $params['type'] = 'MERCHANT_ID';//分账接收方类型
			    $params['account'] = $webpay['mch_id'];//分账接收方账号
                $params['name'] = $this->getEncrypt($webpay['name'],false,$applet_id); //（加密）分账个人接收方姓名 分账接收方类型是MERCHANT_ID时，是商户全称（必传）
			}else{
			    $params['type'] = 'PERSONAL_OPENID';//分账接收方类型
			    $params['account'] = $account;//分账接收方账号
			}
			$params['relation_type'] = 'PARTNER'; ////与分账方的关系类型 合作伙伴
			$is_isp = false;
			$serial_no = $this->config['serial_no'];
		}
        $params = hema_json($params);
        $url = $this->getUrl('profitsharing/receivers/add');
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params,$is_isp),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
            'Wechatpay-Serial:' . $serial_no,
        ];
        return $this->result(json_decode(Http::post($url, $params,[],$headers),true));
    }
    /**
     * 申请退款API
     */
    public function refunds($transaction_id,$out_refund_no,$refund_fee,$total_fee,$notify_url='',$reason='')
    {
        $params = [
            'transaction_id' => $transaction_id,//微信支付订单号
            'out_refund_no' => $out_refund_no,//退款订单号
            'amount' => [
                'refund' => $refund_fee * 100, // 退款金额，价格:单位分
                'total' => $total_fee * 100, // 订单金额，价格:单位分
                'currency' => 'CNY', //退款币种 只支持人民币：CNY
            ],
        ];
        if($this->config['is_sub'] == 1){
            //服务商
            $params['sub_mchid'] = $this->config['mch_id'];//子商户号
            $is_isp = true;
        }else{
            $is_isp = false;
        }
        !empty($reason) && $params['reason'] = $reason;//退款原因
        !empty($notify_url) && $params['notify_url'] = base_url() . $notify_url;  // 异步通知地址
        
        $params = hema_json($params);
        $url = $this->getUrl('refund/domestic/refunds');
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params,$is_isp),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
        ];
        return $this->result(json_decode(Http::post($url, $params,[],$headers),true));
		
    }
    /**
    * 退款成功异步通知
    */
    public function refundsNotify($Model,$applet_id='')
    {
        //接收微信服务器回调的数据流
        if (!$json = file_get_contents('php://input')) {
            $this->returnHttpCode(false);
        }
        // 将服务器返回的json数据转化为数组
        $result = json_decode($json,true);
        if(empty($applet_id)){
            $this->config = Setting::getItem('webpay',0)['wx'];//平台商户支付参数
        }else{
            $this->config = Setting::getItem('wxpay',$applet_id);//商家商户支付参数
        }
        if($this->config['is_sub'] == 1){
            //服务商
            $api_key = $this->isp_config['api_key'];
            //判断平台证书是否过期
        	if($this->isp_config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates()){
        	        $this->returnHttpCode(false,$this->error);//更新失败
        	    }
        	}
        }else{
            //直连商户
            $api_key = $this->config['api_key'];
            //判断平台证书是否过期
        	if($this->config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates(false,$applet_id)){
        	        $this->returnHttpCode(false,$this->error);//更新失败
        	    }
        	}
        }
        if(!$decrypt = new AesUtil($api_key)){
		    $this->returnHttpCode(false,$decrypt->getError());
		}
        if(!$res = $decrypt->decryptToString($result['resource']['associated_data'], $result['resource']['nonce'], $result['resource']['ciphertext'])){
		    $this->returnHttpCode(false,$decrypt->getError());
		}
		$data = json_decode($res,true);
		// 订单信息
		if(!$order = $Model->refundDetail($data['out_refund_no'])){
		    $this->returnHttpCode(false,'订单不存在');
		}
		
        if($data['refund_status'] == 'SUCCESS') {
            // 更新订单状态
            $order->updateRefundStatus($data['refund_id']);
            $this->returnHttpCode(true);// 返回状态
        }
        $this->returnHttpCode(false, '退款失败');
    }
    /**
     * Native下单API
     * $out_trade_no=订单号, $total=支付金额，$attach=订单描述 $profit_sharing=是否分账
     */
    public function native($out_trade_no,$total,$notify_url,$attach='订单支付',$profit_sharing = false)
    {
        $params = [
			'description' => $attach,//商品描述
			'out_trade_no' => $out_trade_no,//商户订单号
			'attach' => $attach,//附加数据
			'notify_url' => base_url() . $notify_url, //通知地址
			'amount' => [
			    'total' => $total * 100,//订单总金额，单位为分
	        ],
			'scene_info' => [
			    'payer_client_ip' => \request()->ip(),//用户终端IP
	        ],
		];
		if($this->config['is_sub'] == 1){
            //服务商
			$params['sp_appid'] = $this->isp_config['app_id'];//服务商应用ID
			$params['sp_mchid'] = $this->isp_config['mch_id'];//服务商商户号
			//$params['sub_appid'] = $this->config['app_id'];//子商户应用ID
			$params['sub_mchid'] = $this->config['mch_id'];//子商户号
			//$params['payer']['sub_openid'] = $openid; //子用户标识Openid
			$url = $this->getUrl('pay/partner/transactions/native');//服务商
			$is_isp = true;
		}else{
		    //直连商户
			$params['appid'] = $this->config['app_id'];//小程序ID
			$params['mchid'] = $this->config['mch_id'];//商户号
			$url = $this->getUrl('pay/transactions/native');//直连商户
			$is_isp = false;
		}
		//判断是否开启分账
		$divide = Setting::getItem('divide',0);
		if($profit_sharing or $divide['extract'] > 0){
		    $params['settle_info']['profit_sharing'] = true; //开启分账
		}
        $params = hema_json($params);
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params,$is_isp),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
        ];
        $result = json_decode(Http::post($url, $params,[],$headers),true);
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		if(!isset($result['code_url'])){
			$this->error = 'Native下单接口请求失败';
			return false;
		}
	    return $result['code_url'];
    }
    /**
     * JSAPI下单API
     * $out_trade_no=订单号, $total=支付金额，$openid=微信用户ID, ,$attach=订单描述
     *  $profit_sharing=是否分账（有配送费要分账时传递）
     */
    public function jsapi($out_trade_no,$total,$openid,$notify_url,$attach='订单支付',$profit_sharing = false)
    {
        
        $params = [
			'description' => $attach,//商品描述
			'attach' => $attach,//附加数据
			'out_trade_no' => $out_trade_no,//商户订单号
			'notify_url' => base_url() . $notify_url, //通知地址
			'amount' => [
			    'total' => (int)($total * 100),//订单总金额，单位为分
	        ],
			'scene_info' => [
			    'payer_client_ip' => \request()->ip(),//用户终端IP
	        ],
		];
		if($this->config['is_sub'] == 1){
            //服务商
			$params['sp_appid'] = $this->isp_config['app_id'];//服务商应用ID
			$params['sp_mchid'] = $this->isp_config['mch_id'];//服务商商户号
			$params['sub_appid'] = $this->config['app_id'];//子商户应用ID
			$params['sub_mchid'] = $this->config['mch_id'];//子商户号
			$params['payer']['sub_openid'] = $openid; //子用户标识Openid
			$url = $this->getUrl('pay/partner/transactions/jsapi');//服务商
			$is_isp = true;
		}else{
		    //直连商户
			$params['appid'] = $this->config['app_id'];//小程序ID
			$params['mchid'] = $this->config['mch_id'];//商户号
			$params['payer']['openid'] = $openid; //用户标识Openid
			$url = $this->getUrl('pay/transactions/jsapi');//直连商户
			$is_isp = false;
		}
		//判断是否开启分账
		$divide = Setting::getItem('divide',0);
		if($profit_sharing or $divide['extract'] > 0){
		    $params['settle_info']['profit_sharing'] = true; //开启分账
		}
        $params = hema_json($params);
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params,$is_isp),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
        ];
        $result = json_decode(Http::post($url, $params,[],$headers),true);
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		if(!isset($result['prepay_id'])){
			$this->error = 'JSAPI下单接口请求失败';
			return false;
		}
		$data = [
	        'timeStamp' => (string)time(),
	        'nonceStr' => $this->nonce(),
	        'package' => 'prepay_id=' . $result['prepay_id'],
	        'signType' => 'RSA',
	    ];
	    $data['paySign'] = $this->paySign($data);
	    return $data;
    }
    
    /**
     * 支付成功异步通知
     */
    public function notify($Model,$applet_id,$method='edit')
    {
        //接收微信服务器回调的数据流
        if (!$json = file_get_contents('php://input')) {
            $this->returnHttpCode(false);
        }
        // 将服务器返回的json数据转化为数组
        $result = json_decode($json,true);
        if(empty($applet_id)){
            $this->config = Setting::getItem('webpay',0)['wx'];//平台商户支付参数
        }else{
            $this->config = Setting::getItem('wxpay',$applet_id);//商家商户支付参数
        }
        if($this->config['is_sub'] == 1){
            //服务商
            $api_key = $this->isp_config['api_key'];
            //判断平台证书是否过期
        	if($this->isp_config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates()){
        	        $this->returnHttpCode(false,$this->error);//更新失败
        	    }
        	}
        }else{
            //直连商户
            $api_key = $this->config['api_key'];
            //判断平台证书是否过期
        	if($this->config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates(false,$applet_id)){
        	        $this->returnHttpCode(false,$this->error);//更新失败
        	    }
        	}
        }
        if(!$decrypt = new AesUtil($api_key)){
		    $this->returnHttpCode(false,$decrypt->getError());
		}
        if(!$res = $decrypt->decryptToString($result['resource']['associated_data'], $result['resource']['nonce'], $result['resource']['ciphertext'])){
		    $this->returnHttpCode(false,$decrypt->getError());
		}
		$data = json_decode($res,true);
		// 订单信息
		if(!$order = $Model->payDetail($data['out_trade_no'])){
		    $this->returnHttpCode(false,'订单不存在');
		}
		
        //判断支付状态
        if($data['trade_state'] == 'SUCCESS') {
            if($method == 'add'){
                $Model->updatePayStatus($data['transaction_id'],$order);
                Cache::delete($data['out_trade_no']);
            }else{
                // 更新订单状态
                $order->updatePayStatus($data['transaction_id']);
            }
            // 返回状态
            $this->returnHttpCode(true);
        }
        // 返回状态
        $this->returnHttpCode(false, '支付失败');
    }
    
    /**
     * 特约商户进件
     * 频率限制：15/s
    */
    public function applyment($params)
    {
        //********************* 数据加密 *****************
        
        //管理员姓名
        if(!$result = $this->getEncrypt($params['contact_info']['contact_name'])){
            return false;
        }
        $params['contact_info']['contact_name'] = $result;
        //管理员电话
        if(!$result = $this->getEncrypt($params['contact_info']['mobile_phone'])){
            return false;
        }
        $params['contact_info']['mobile_phone'] = $result;
        //管理员邮箱
        if(!$result = $this->getEncrypt($params['contact_info']['contact_email'])){
            return false;
        }
        $params['contact_info']['contact_email'] = $result;
        //身份证姓名
        if(!$result = $this->getEncrypt($params['subject_info']['identity_info']['id_card_info']['id_card_name'])){
            return false;
        }
        $params['subject_info']['identity_info']['id_card_info']['id_card_name'] = $result;
        //身份证号
        if(!$result = $this->getEncrypt($params['subject_info']['identity_info']['id_card_info']['id_card_number'])){
            return false;
        }
        $params['subject_info']['identity_info']['id_card_info']['id_card_number'] = $result;
        //银行开户名称
        if(!$result = $this->getEncrypt($params['bank_account_info']['account_name'])){
            return false;
        }
        $params['bank_account_info']['account_name'] = $result;
        //银行账号
        if(!$result = $this->getEncrypt($params['bank_account_info']['account_number'])){
            return false;
        }
        $params['bank_account_info']['account_number'] = $result;
        //********************* 上传图片 *****************
        //营业执照
        if(!$result = $this->upload($params['subject_info']['business_license_info']['license_copy'])){
            return false;
        }
        $params['subject_info']['business_license_info']['license_copy'] = $result;
        //身份证正面
        if(!$result = $this->upload($params['subject_info']['identity_info']['id_card_info']['id_card_copy'])){
            return false;
        }
        $params['subject_info']['identity_info']['id_card_info']['id_card_copy'] = $result;
        //身份证反面
        if(!$result = $this->upload($params['subject_info']['identity_info']['id_card_info']['id_card_national'])){
            return false;
        }
        $params['subject_info']['identity_info']['id_card_info']['id_card_national'] = $result;
        //特殊资质
        if(!$result = $this->upload($params['settlement_info']['qualifications'][0])){
            return false;
        }
        $params['settlement_info']['qualifications'][0] = $result;
        //门头照片
        if(!$result = $this->upload($params['business_info']['sales_info']['biz_store_info']['store_entrance_pic'][0])){
            return false;
        }
        $params['business_info']['sales_info']['biz_store_info']['store_entrance_pic'][0] = $result;
        //店内照片
        if(!$result = $this->upload($params['business_info']['sales_info']['biz_store_info']['indoor_pic'][0])){
            return false;
        }
        $params['business_info']['sales_info']['biz_store_info']['indoor_pic'][0] = $result;
        $params = hema_json($params);
        $url = $this->getUrl('applyment4sub/applyment/');
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',$params),
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:' . $this->isp_config['mch_id'],
            'Wechatpay-Serial:' . $this->isp_config['serial_no'],
        ];
        $result = json_decode(Http::post($url, $params,[],$headers),true);
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		return $result['applyment_id'];
    }
    /**
     * 查询申请单状态
    */
    public function queryApplyment($no,$is_applyment_id = false)
    {
        if($is_applyment_id){
            $path = 'applyment_id/' . $no;//通过申请单号查询申请状态（官方返回的编号）
        }else{
            $path = 'business_code/' . $no;//通过业务申请编号查询申请状态（第三方自定义的编号）
        }
        $url = $this->getUrl('applyment4sub/applyment/'.$path);
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'GET'),
            'Accept:application/json',
        ];
        return $this->result(json_decode(Http::get($url, [],[],$headers),true));
    }

    /**
     * 获取平台证书列表
     */
    private function certificates($is_isp=true,$applet_id='')
    {
        $url = $this->getUrl('certificates');
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'GET','',$is_isp),
            'Accept:application/json',
            'User-Agent:https://zh.wikipedia.org/wiki/User_agent',
        ];
        $result = json_decode(Http::get($url,[],[],$headers),true);
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		//验证是否获取到了数据
		if(!isset($result['data']) and sizeof($result['data']) == 0){
		    $this->error = '未获取到可用的平台证书';
		    return false;
		}
		$result = $result['data'][0];//获取证书列表中的第一个数据
		if($is_isp){
		    $api_key = $this->isp_config['api_key'];
		}else{
		    $api_key = $this->config['api_key'];
		}
		if(!$decrypt = new AesUtil($api_key)){
		    $this->error = $decrypt->getError();
		    return false;
		}
		if(!$res = $decrypt->decryptToString($result['encrypt_certificate']['associated_data'], $result['encrypt_certificate']['nonce'], $result['encrypt_certificate']['ciphertext'])){
		    $this->error = $decrypt->getError();
		    return false;
		}
		//计算到期时间
		$expire_time = explode('T',$result['expire_time']);
		$expire_time = strtotime($expire_time[0]);
		$model = new Setting;
		//更新平台证书
		if($is_isp){
		    $this->isp_config['certificates'] = $res;
    		$this->isp_config['serial_no'] = $result['serial_no'];
    		$this->isp_config['expire_time'] = $expire_time;
            $model->edit('wxpayisp',$this->isp_config,0); //保存到数据库
            return true;
		}
		//更新特约商户 平台证书
		$this->config['certificates'] = $res;
    	$this->config['serial_no'] = $result['serial_no'];
    	$this->config['expire_time'] = $expire_time;
		if(empty($applet_id)){
		    $config = Setting::getItem('webpay',0);
		    $config['wx']['certificates'] = $res;
    		$config['wx']['serial_no'] = $result['serial_no'];
    		$config['wx']['expire_time'] = $expire_time;
            $model->edit('webpay',$config,0); //保存到数据库
		}else{
            $model->edit('wxpay',$this->config,$applet_id); //保存到数据库
		}
		return true;
    }
    
    /**
     * 图片上传API
    */
    private function upload($file_path)
    {
        $file = file_get_contents(web_path() . 'uploads/' . $file_path);//获取网络图片
        $meta =[
            'filename' => $file_path,
            'sha256' => hash('sha256',$file),
        ];
        $url = $this->getUrl('merchant/media/upload');
        $boundary = uniqid();//随机数
        $headers = [
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $this->sign($url,'POST',hema_json($meta)),
            'Accept:application/json',
            'Content-Type:multipart/form-data;boundary=' . $boundary,
        ];
        $params = '--' . $boundary . "\r\n";
        $params .= 'Content-Disposition:form-data; name="meta"' . "\r\n";
        $params .= 'Content-Type:application/json' . "\r\n\r\n";
        $params .= hema_json($meta) . "\r\n";
        $params .= '--' . $boundary . "\r\n";
        $params .= 'Content-Disposition:form-data;name="file";filename="' . $meta['filename'] . '"' . "\r\n";
        $params .= 'Content-Type:image/jpg' . "\r\n\r\n";
        $params .= $file . "\r\n";
        $params .= '--' . $boundary . '--' . "\r\n";
        $result = json_decode(Http::post($url, $params,[],$headers),true);
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		if(isset($result['media_id'])){
		    return $result['media_id'];
		}
		$this->error = '图片上传失败';
		return false;
    }
    
    /**
     * 调起支付签名
    */
    private function paySign($data)
    {
        $params = $this->config['app_id'] . "\n" .
        $data['timeStamp'] . "\n" .
        $data['nonceStr'] . "\n" .
        $data['package'] . "\n"; 
        if($this->config['is_sub'] == 1){
            //服务商
            $private_key = $this->isp_config['key_pem']; //API私有证书
        }else{
            //直连商户
            $private_key = $this->config['key_pem']; //API私有证书
        }
        $raw_sign = '';
        openssl_sign($params, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        return base64_encode($raw_sign);
    }
    
    /**
     * 生成签名
     * $http_method = HTTP请求的方法（GET,POST,PUT
     * serial_no 为你的商户证书序列号
     * $mch_private_key = 是商户API私钥，在商户平台下载的证书文件包含该文件，名称为apiclient_key.pem
     * $is_isp 是否为服务商操作
     */
    private function sign($url,$http_method,$body='',$is_isp=true) 
    {
        $timestamp = time(); //时间戳
        $nonce = $this->nonce(); //随机字符串
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $params = $http_method . "\n" .
        $canonical_url . "\n" .
        $timestamp . "\n" .
        $nonce . "\n" .
        $body . "\n"; 
        if($is_isp){
            $mchid = $this->isp_config['mch_id']; //商户号
            $serial_no = $this->isp_config['api_serial_no']; //API证书序列号
            $mch_private_key = $this->isp_config['key_pem']; //API私有证书
        }else{
            $mchid = $this->config['mch_id']; //商户号
            $serial_no = $this->config['api_serial_no']; //API证书序列号
            $mch_private_key = $this->config['key_pem']; //API私有证书
        }
        $raw_sign = '';
        openssl_sign($params, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);    
        //$schema = 'WECHATPAY2-SHA256-RSA2048';
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',$mchid, $nonce, $timestamp, $serial_no, $sign);
        return $token;
    }
    /**
     * 敏感信息加密
     */
    private function getEncrypt($str,$is_isp = true,$applet_id='') 
    {
    	//判断平台证书是否过期
    	if($is_isp){
    	    //服务商
        	if($this->isp_config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates()){
        	        return false;//更新失败
        	    }
        	}
        	$public_key = $this->isp_config['certificates'];//平台证书
    	}else{
    	    //直连商户
    	    if($this->config['expire_time'] < time()){
        	    //更新平台证书
        	    if(!$this->certificates(false,$applet_id)){
        	        return false;//更新失败
        	    }
        	}
        	$public_key = $this->config['certificates'];//平台证书
    	}
    	$encrypted = '';
    	if (!openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
    		$this->error = '敏感信息加密失败';
		    return false;
    	}
    	return base64_encode($encrypted);//base64编码
    }
    /**
     * 生成随机字符串
     */
    private function nonce() 
    {
        return md5(uniqid());
    }
    /*
     * 拼接请求域名接口
     */
    private function getUrl($url) 
    {
        return $this->api_url . '/' . $this->version . '/' . $url;
    }
    /**
     * 获取Headers数据
     */
    private function getHeaders()
    {
        $headers = array();
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) === 'HTTP_') {
                $key = substr($key, 5);
                $key = str_replace('_', ' ', $key);
                $key = str_replace(' ', '-', $key);
                $key = strtolower($key);
                $headers[$key] = $value;
            }
        }
        return $headers;
    }
    /**
     * 返回状态给微信服务器
     */
    private function returnHttpCode($is_success = true, $msg = '失败')
    {
        $json = hema_json([
            'code' => $is_success ? 'SUCCESS' : 'FAIL',
            'message' => $is_success ? '成功' : $msg,
        ]);
        if($is_success){
            header('HTTP/1.1 200 OK');
        }else{
            header('HTTP/1.1 404 Not Found');
        }
        die($json);
    }
    /**
     * 请求数据验证
     **/
    private function result($result)
    {
        if(isset($result['code'])){
			$this->error = 'code：' . $result['code'] . '，msg：' . $result['message'];
			return false;
		}
		return $result;
    }
    public function getError()
    {
        return $this->error;
    }
     
    /********** V2接口 **********/
    /**
     * 付款码支付
     * $auth_code=付款码 $order_no=订单号 $openid=微信用户ID, $total_fee=支付金额, ,$attach=订单描述 $divide=是否分账 
     */
    public function micropay($auth_code,$order_no, $total_fee,$profit_sharing = false,$attach = '订单支付')
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $openid);
		// API参数
		$params = [
		    'auth_code' => $auth_code,//付款码支付
			'attach' => $attach,
			'nonce_str' => $nonceStr,//随机字符串
			'body' => $attach,//商品描述
			'out_trade_no' => $order_no,//商户订单号
			'total_fee' => $total_fee * 100, // 价格:单位分
			'spbill_create_ip' => \request()->ip(),//服务终端IP
		];
		if($this->config['is_sub'] == 1){
            //服务商统一下单
            $values = Setting::getItem('wxpayisp',0);
			$this->config['api_key'] = $this->isp_config['api_key'];//服务商商户的密钥
			$params['appid'] = $this->isp_config['app_id'];//服务商商户的APPID
			$params['mch_id'] = $this->isp_config['mch_id'];//服务商商户号
			$params['sub_appid'] = $this->config['app_id'];//当前调起支付的小程序APPID
			$params['sub_mch_id'] = $this->config['mch_id'];//服务商分配的子商户号
		}else{
			$params['appid'] = $this->config['app_id'];//小程序ID
			$params['mch_id'] = $this->config['mch_id'];//商户号
		}
        //判断是否开启分账
		$divide = Setting::getItem('divide',0);
		if($profit_sharing or $divide['extract'] > 0){
		    $params['profit_sharing'] = 'Y';//开启分账
		}
		
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        $url = 'https://api.mch.weixin.qq.com/pay/micropay';// 请求API
        $result = $this->postXmlCurl($this->toXml($params), $url);
        $prepay = $this->fromXml($result);
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            die(hema_json(['code' => -10, 'msg' => $prepay['return_msg']]));
        }
        //判断付款码支付时，用户支付中，需要输入密码
        if ($prepay['result_code'] === 'USERPAYING') {
            return false;
        }
        if ($prepay['result_code'] === 'FAIL') {
            die(hema_json(['code' => -10, 'msg' => $prepay['err_code_des']]));
        }
        return $prepay['transaction_id'];//支付交易号
    } 
    
    /**
     * 查询付款码支付结果是否成功
     */
    public function orderquery($out_trade_no)
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time);
		// API参数
		$params = [
		    'out_trade_no' => $out_trade_no,
			'nonce_str' => $nonceStr,//随机字符串
		];
		if($this->config['is_sub'] == 1){
            //服务商统一下单
			$this->config['api_key'] = $this->isp_config['api_key'];//服务商商户的密钥
			$params['appid'] = $this->isp_config['app_id'];//服务商商户的APPID
			$params['mch_id'] = $this->isp_config['mch_id'];//服务商商户号
			$params['sub_appid'] = $this->config['app_id'];//当前调起支付的小程序APPID
			$params['sub_mch_id'] = $this->config['mch_id'];//服务商分配的子商户号
		}else{
			$params['appid'] = $this->config['app_id'];//小程序ID
			$params['mch_id'] = $this->config['mch_id'];//商户号
		}		
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/pay/orderquery';
        $result = $this->postXmlCurl($this->toXml($params), $url);
        $prepay = $this->fromXml($result);
        // 请求失败
        if ($prepay['return_code'] === 'SUCCESS' AND $prepay['result_code'] === 'SUCCESS') {
            return $prepay['trade_state'];
        }
        return 'ERROR';
    }

    /**
     * 输出xml字符
     */
    private function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
    /**
     * 将xml转为array
     */
    private function fromXml($xml)
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 以post方式提交xml到对应的接口url
     */
    private function postXmlCurl($xml, $url, $cert = false, $second = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);// 设置超时时间
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);//https请求 不验证证书和host
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);//严格校验
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);// 要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_POST, TRUE);// post提交方式
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);// 是否返回请求头
        //判断是否使用证书
        if($cert){
            $path = root_path() . '/extend/hema/wechat/cert/';
            file_put_contents($path . 'apiclient_cert.pem',$this->config['cert_pem']);
            file_put_contents($path . 'apiclient_key.pem',$this->config['key_pem']);
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,$path . 'apiclient_cert.pem');
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,$path . 'apiclient_key.pem');
        }
        $data = curl_exec($ch);// 运行curl
        curl_close($ch);
        return $data;
    }
     
    /**
     * 生成签名MD5
     */
    private function makeSign($values)
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->toUrlParams($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->config['api_key'];
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    } 
    
    /**
     * 格式化参数格式化成url参数
     */
    private function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }

}
